import csv
from collections import defaultdict
import pickle
import os
from netorchestr.eventlog import OLogItem
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

color_Bar = np.array([[209,42,32],[80,138,178],[250,192,61],[213,168,130],[179,106,111],[161,208,199],[80,138,178]])/255

class VnffgInfo:
    def __init__(self,sfc_info_file):
        with open(sfc_info_file, 'rb') as f:
            sfc_info = pickle.load(f)
        
        self.vnffg_id = sfc_info['vnffg_id']
        self.ue_list = sfc_info['ue_list']
        self.ue_ip_list = [str(ip) for ip in sfc_info['ue_ip_list']]
        self.vnf_list = sfc_info['vnf_list']
        self.vnf_ip_list = [str(ip) for ip in sfc_info['vnf_ip_list']]
        self.ue_ip_name_map = dict(zip(self.ue_ip_list, self.ue_list))
        self.vnf_ip_name_map = dict(zip(self.vnf_ip_list, self.vnf_list))
        self.node_ip_name_map = {**self.ue_ip_name_map, **self.vnf_ip_name_map}
        self.control_time = sfc_info['control_time']
        self.actural_delay = sfc_info['actural_delay']
        self.predict_delay = sfc_info['predict_delay']
        self.queue_length = sfc_info['queue_length']
        self.process_rate = sfc_info['process_rate']
        self.revenue_list = sfc_info['revenue_list']
        self.cost_list = sfc_info['cost_list']
        self.aim_delay = sfc_info['aim_delay']
        self.aim_overrate = sfc_info['aim_overrate']
        self.arrive_rate = sfc_info['arrive_rate']
        self.kp = sfc_info['kp']
        self.ki = sfc_info['ki']
        self.kd = sfc_info['kd']
        self.kff = sfc_info['kff']
        self.pkt_delay_receiver = sfc_info['pkt_delay_receiver']
        self.over_delay_num = sfc_info['over_delay_num']


def plot_duration_curves(csv_file_path, ue_list, vnf_list):
    """
    从CSV中提取延迟列并绘制曲线+区间阴影：
    - 0 ~ RecvDuration：白色阴影
    - RecvDuration ~ SendDuration：灰色阴影
    - SendDuration ~ FinalDuration：白色阴影
    :param csv_file_path: CSV文件路径
    :param ue_list: UE节点列表（用于定位FinalDuration列）
    :param vnf_list: VNF节点列表（用于定位Recv/SendDuration列）
    """
    # ---------------------- 1. 读取CSV并筛选目标列（保留你的原有逻辑） ----------------------
    df = pd.read_csv(csv_file_path, skip_blank_lines=True)
    
    # 定义目标列：VNF的Recv/Send + UE1的Final
    target_columns = []
    for vnf in vnf_list:
        target_columns.append(f'{vnf}_RecvDuration')  # VNF接收延迟
        target_columns.append(f'{vnf}_SendDuration')  # VNF发送延迟
    target_columns.append(f"{ue_list[1]}_FinalDuration")  # UE1最终延迟
    
    # 筛选存在的列，转换为数值类型（处理空值）
    existing_target_columns = [col for col in target_columns if col in df.columns]
    df_target = df[existing_target_columns].copy()
    for col in df_target.columns:
        df_target[col] = pd.to_numeric(df_target[col], errors='coerce')
    
    # 横轴：数据行索引（与你的需求一致）
    x = np.arange(len(df_target))
    if len(x) == 0:
        print("CSV无有效数据，无法绘图")
        return


    # ---------------------- 2. 关键：按节点分组匹配延迟列（为阴影做准备） ----------------------
    # 1. 分离VNF列和Final列（Final列仅ue_list[1]一个）
    vnf_cols = [col for col in existing_target_columns if any(vnf in col for vnf in vnf_list)]
    final_col = next((col for col in existing_target_columns if f"{ue_list[1]}_FinalDuration" in col), None)
    
    # 2. 为每个VNF分组：匹配其Recv和Send列（确保一一对应）
    vnf_duration_map = {}  # 格式：{vnf_name: (recv_col, send_col)}
    for vnf in vnf_list:
        recv_col = f'{vnf}_RecvDuration'
        send_col = f'{vnf}_SendDuration'
        if recv_col in existing_target_columns and send_col in existing_target_columns:
            vnf_duration_map[vnf] = (recv_col, send_col)
    
    # 3. 准备颜色和线型（保留你的原有配置，补充阴影边框色）
    # total_lines = len(existing_target_columns)
    # color_map = plt.cm.Set3(np.linspace(0, 1, total_lines))  # 曲线颜色
    color_map = color_Bar
    line_styles = {'RecvDuration': '-', 'SendDuration': '--', 'FinalDuration': ':'}
    node_colors = {node: color_map[node_idx] for node_idx, node in enumerate(vnf_list+[ue_list[1]])}  # 节点颜色


    # ---------------------- 3. 初始化画布（保留你的原有样式） ----------------------
    plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
    plt.rcParams['axes.unicode_minus'] = False
    plt.figure(figsize=(10, 3))


    # ---------------------- 4. 核心：绘制阴影（按区间填充） ----------------------
    # 先处理VNF的三个区间阴影（0-Recv/Recv-Send/Send-Final）
    for vnf_idx, (vnf, (recv_col, send_col)) in enumerate(vnf_duration_map.items()):
        # 提取当前VNF的有效数据（过滤NaN，确保区间对应同一行）
        valid_mask = df_target[recv_col].notna() & df_target[send_col].notna()
        if final_col:  # 若存在Final列，需同时满足Final数据有效
            valid_mask = valid_mask & df_target[final_col].notna()
        
        valid_x = x[valid_mask]
        valid_recv = df_target.loc[valid_mask, recv_col]
        valid_send = df_target.loc[valid_mask, send_col]
        valid_final = df_target.loc[valid_mask, final_col] if final_col else None

        if len(valid_x) == 0:
            print(f"VNF[{vnf}]无有效延迟数据，跳过阴影绘制")
            continue

        # 为当前VNF分配颜色（与后续曲线颜色一致，建立对应关系）
        # 取该VNF Recv列在existing_target_columns中的索引，匹配color_map
        recv_col_idx = existing_target_columns.index(recv_col)
        vnf_color = color_map[recv_col_idx]


        # 4.1 阴影1：0 ~ RecvDuration → 白色
        # if vnf_idx == 0:
        #     plt.fill_between(
        #         x=valid_x,
        #         y1=0,                  # 下边界：0
        #         y2=valid_recv,         # 上边界：RecvDuration
        #         color='white',         # 填充色：白色
        #         alpha=0.6,             # 半透明：避免遮挡
        #         # edgecolor=vnf_color,   # 边框色：与VNF曲线一致（区分节点）
        #         linewidth=0.8,         # 边框线宽：细线条不抢焦点
        #         label=f'{vnf} - 0~Recv' if vnf_idx == 0 else ""  # 仅第一个VNF显示区间图例（避免重复）
        #     )

        # 4.2 阴影2：RecvDuration ~ SendDuration → 灰色
        plt.fill_between(
            x=valid_x,
            y1=valid_recv,         # 下边界：RecvDuration
            y2=valid_send,         # 上边界：SendDuration
            color='gray',          # 填充色：灰色
            alpha=0.6,
            # edgecolor=vnf_color,
            linewidth=0.8,
            label=None             # 无区间图例
        )


    # ---------------------- 5. 绘制延迟曲线（保留你的原有逻辑，调整颜色索引） ----------------------
    for idx, col in enumerate(existing_target_columns):
        # 提取节点名和延迟类型
        if any(vnf in col for vnf in vnf_list):
            node = col.split('_')[0]  # VNF节点名（如"vnf1"）
        else:
            node = ue_list[1]  # Final列对应的UE节点（如"ue1"）
        
        dur_type = col.split('_')[1]  # 延迟类型（Recv/Send/Final）
        
        # 过滤无效数据
        valid_data = df_target[col].dropna()
        valid_x = x[df_target[col].notna()]
        if len(valid_x) == 0:
            print(f"列[{col}]无有效数据，跳过曲线绘制")
            continue
        
        # 绘制曲线（颜色从color_map取，与阴影边框对应）
        plt.plot(
            valid_x, valid_data,
            color=node_colors.get(node, 'black'),
            linestyle=line_styles.get(dur_type, '-'),
            linewidth=1,
            label=f'{node} - {dur_type}'  # 图例：节点-延迟类型
        )


    # ---------------------- 6. 图表美化与保存（保留你的原有配置） ----------------------
    plt.xlabel('数据行索引（数据包编号）', fontsize=12)
    plt.ylabel('延迟值（ms）', fontsize=12)
    plt.title(f'各节点延迟曲线与区间阴影\n（0~Recv白色 | Recv~Send灰色 | Send~Final白色）', fontsize=14, pad=20)


    # 获取当前的图例句柄和标签并翻转顺序（保持VNF在前，UE在后）
    handles, labels = plt.gca().get_legend_handles_labels()
    handles = handles[::-1]
    labels = labels[::-1]
    
    # 图例（图内右上角，保留你的配置）
    plt.legend(
        handles, labels,
        loc='upper right',
        fontsize=10,
        ncol=1,
        frameon=True,
        fancybox=True,
        shadow=False,
        bbox_transform=plt.gca().transAxes
    )

    # 网格与保存
    plt.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
    plt.tight_layout()
    save_path = os.path.join(os.path.dirname(csv_file_path), 
                             f"{os.path.basename(csv_file_path).split('.')[0]}_duration_curves.png")
    plt.savefig(save_path, dpi=300, bbox_inches='tight')
    save_path = os.path.join(os.path.dirname(csv_file_path), 
                             f"{os.path.basename(csv_file_path).split('.')[0]}_duration_curves.pdf")
    plt.savefig(save_path, bbox_inches='tight', pad_inches=0.5)
    plt.close()

    print(f"带阴影的曲线已保存至 {save_path}")
    print(f"绘制的延迟列：{existing_target_columns}")
    print(f"横轴长度：{len(x)}（与CSV有效行数一致）")

def parse_log_file(log_file_path, csv_file_path, vnffg_info: VnffgInfo):
    # 如果csv_file_path存在，则删除该文件
    if os.path.exists(csv_file_path):
        os.remove(csv_file_path)
        print(f"已删除旧的CSV文件：{csv_file_path}")
    
    # 提取该链信息

    # 用于存储每个数据包在各个节点的时间信息
    packet_data = defaultdict(lambda: defaultdict(dict))
    
    # 读取日志文件
    with open(log_file_path, 'r', encoding='utf-8') as f:
        for line in f:
            items = line.strip().split()
            if len(items) == 13:
                log_item = OLogItem(
                    event=items[0], time=items[1], 
                    from_node=items[2], to_node=items[3], 
                    pkt_type=items[4], pkt_size=int(items[5]), 
                    flags=items[6], fid=items[7], 
                    src_addr=items[8], dst_addr=items[9], 
                    seq_num=items[10], pkt_id=items[11], 
                    pkt_delay=items[12]
                )
                
                # 根据事件类型记录时间
                if log_item.event == '-' and log_item.to_node == vnffg_info.ue_list[1]:  # 发送事件
                    if log_item.pkt_id not in packet_data:
                        packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[log_item.src_addr]]['send_time'] = log_item.time
                        packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[log_item.src_addr]]['send_duration'] = log_item.pkt_delay
                    else:
                        try:
                            # 获取在vnf_ip_name_map中排在log_item.dst_addr的前一个ip地址
                            pre_vnf_ip = vnffg_info.vnf_ip_list[vnffg_info.vnf_ip_list.index(log_item.dst_addr)-1]
                            packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[pre_vnf_ip]]['send_time'] = log_item.time
                            packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[pre_vnf_ip]]['send_duration'] = log_item.pkt_delay
                        except:
                            pre_vnf_ip = vnffg_info.vnf_ip_list[-1]
                            packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[pre_vnf_ip]]['send_time'] = log_item.time
                            packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[pre_vnf_ip]]['send_duration'] = log_item.pkt_delay
                            
                elif log_item.event == '+' and log_item.to_node == vnffg_info.ue_list[1]:  # 接收事件
                    packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[log_item.dst_addr]]['receive_time'] = log_item.time
                    packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[log_item.dst_addr]]['recv_duration'] = log_item.pkt_delay
                elif log_item.event == 'r' and log_item.to_node == vnffg_info.ue_list[1]:  # 最终接收事件
                    packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[log_item.dst_addr]]['final_receive_time'] = log_item.time
                    packet_data[log_item.pkt_id][vnffg_info.node_ip_name_map[log_item.dst_addr]]['final_duration'] = log_item.pkt_delay
    
    # 定义节点顺序
    nodes_order = vnffg_info.ue_list + vnffg_info.vnf_list
    # 获取每个数据包的发送时间
    pkt_id_sendtime_map = {}
    last_sent_time = 0
    for packet_id in list(packet_data.keys()):
        send_time = packet_data[packet_id][nodes_order[0]]["send_time"]
        if send_time == last_sent_time:
            packet_data.pop(packet_id)
            continue
        last_sent_time = send_time
        pkt_id_sendtime_map[packet_id] = send_time
    # 按照sendtime对pkt_id_sendtime_map排序
    pkt_id_sendtime_map = dict(sorted(pkt_id_sendtime_map.items(), key=lambda item: float(item[1])))
    # 按照该键值顺序对packet_data排序
    packet_data = {k: packet_data[k] for k in pkt_id_sendtime_map.keys()}
    
    # 写入CSV文件
    with open(csv_file_path, 'w', newline='', encoding='utf-8') as csvfile:
        fieldnames = ['PacketID']
        for node in nodes_order:
            fieldnames.extend([f'{node}_ReceiveTime', f'{node}_SendTime', f'{node}_RecvDuration', f'{node}_SendDuration', f'{node}_FinalDuration'])
        
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        
        # 为每个数据包写入数据
        for packet_id, node_data in packet_data.items():
            row = {'PacketID': packet_id}
            
            for node in nodes_order:
                if node in node_data:
                    row[f'{node}_ReceiveTime'] = node_data[node].get('receive_time',
                                                                     node_data[node].get('final_receive_time', ''))
                    row[f'{node}_SendTime'] = node_data[node].get('send_time', '')
                    row[f'{node}_RecvDuration'] = node_data[node].get('recv_duration', '')
                    row[f'{node}_SendDuration'] = node_data[node].get('send_duration', '')
                    row[f'{node}_FinalDuration'] = node_data[node].get('final_duration', '')
                else:
                    row[f'{node}_ReceiveTime'] = ''
                    row[f'{node}_SendTime'] = ''
                    row[f'{node}_RecvDuration'] = ''
                    row[f'{node}_SendDuration'] = ''
                    row[f'{node}_FinalDuration'] = ''
                    
            
            writer.writerow(row)
    
    print(f"数据已成功写入 {csv_file_path}")
    
def plot_vnffg_self_info(csv_file_path, vnffg_info: VnffgInfo):
    # 绘制单个VNFFG的曲线
    plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
    plt.rcParams['axes.unicode_minus'] = False
    
    plt.figure(figsize=(10, 3))
    for vnfEm in vnffg_info.queue_length.keys():
        plt.plot(vnffg_info.control_time, vnffg_info.queue_length[vnfEm], label=f"{vnfEm}_queue")
    plt.xlabel("时间（ms）")
    plt.ylabel("缓存队列长度（包）")
    plt.legend()
    plt.tight_layout()
    save_path = os.path.join(os.path.dirname(csv_file_path), 
                             f"{os.path.basename(csv_file_path).split('.')[0]}_queue.png")
    plt.savefig(save_path, dpi=300, bbox_inches='tight')
    save_path = os.path.join(os.path.dirname(csv_file_path), 
                             f"{os.path.basename(csv_file_path).split('.')[0]}_queue.pdf")
    plt.savefig(save_path, bbox_inches='tight', pad_inches=0.5)
    plt.close()
    
    plt.figure(figsize=(10, 3))
    for vnfEm in vnffg_info.process_rate.keys():
        plt.plot(vnffg_info.control_time, vnffg_info.process_rate[vnfEm], label=f"{vnfEm}_rate")
    plt.xlabel("时间（ms）")
    plt.ylabel("实时处理速率（包/ms）")
    plt.legend()
    plt.tight_layout()
    save_path = os.path.join(os.path.dirname(csv_file_path), 
                             f"{os.path.basename(csv_file_path).split('.')[0]}_rate.png")
    plt.savefig(save_path, dpi=300, bbox_inches='tight')
    save_path = os.path.join(os.path.dirname(csv_file_path), 
                             f"{os.path.basename(csv_file_path).split('.')[0]}_rate.pdf")
    plt.savefig(save_path, bbox_inches='tight', pad_inches=0.5)
    plt.close()
    
    
    
def plot_vnffgs_info(vnffg_name_data_map, save_path):
    # 绘制所有VNFFG的延迟曲线
    plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
    plt.rcParams['axes.unicode_minus'] = False
    plt.figure(figsize=(10, 5))
    
    for i,vnffg_name in enumerate(vnffg_name_data_map.keys()):
        vnffg_info: VnffgInfo = vnffg_name_data_map[vnffg_name]["vnffg_info"]
        plt.scatter(x=vnffg_info.control_time, 
                    y=vnffg_info.actural_delay, 
                    color=color_Bar[i], 
                    marker=".", s=10, 
                    label=f"{vnffg_name}_actural")
        plt.plot(vnffg_info.control_time, vnffg_info.predict_delay, 
                 color=color_Bar[i], 
                 linewidth=1,
                 label=f"{vnffg_name}_predict")
        plt.plot(vnffg_info.control_time, 
                 [vnffg_info.aim_delay]*len(vnffg_info.control_time), 
                 linestyle="--", color=color_Bar[i], 
                 label=f"{vnffg_name}_aim")
        
    plt.xlabel("时间（ms）")
    plt.ylabel("端到端延迟（ms）")
    plt.legend()
    plt.tight_layout()
    plt.savefig(os.path.join(save_path, f"delay.png"), dpi=300, bbox_inches='tight')
    plt.savefig(os.path.join(save_path, f"delay.pdf"), bbox_inches='tight', pad_inches=0.5)
    plt.close()
    
    
# 使用示例
if __name__ == "__main__":
    result_path = "SfcdSagin_Yilong15Pro_20251102204635819_0"
    log_file = os.path.join(result_path, "SfceSagin_trace.log")
    vnffgs_log_path = os.path.join(result_path,"nfvo_monitor_data","TraceVNFFG")
    vnffg_list = [file for file in os.listdir(vnffgs_log_path) if os.path.isdir(os.path.join(vnffgs_log_path, file))]
    vnffg_name_data_map = defaultdict(lambda: defaultdict(dict))
    for vnffg_name in vnffg_list:
        vnffg_log_path = os.path.join(vnffgs_log_path, vnffg_name)
        vnffg_data_file = [file for file in os.listdir(vnffg_log_path) if file.endswith(".pkl")]
        vnffg_data_file_path = os.path.join(vnffg_log_path, vnffg_data_file[0])
        vnffg_name_data_map[vnffg_name]["vnffg_data_file_path"] = vnffg_data_file_path
        
        vnffg_csv_file_path = os.path.join(vnffg_log_path, f"{vnffg_name}.csv")
        vnffg_info = VnffgInfo(vnffg_data_file_path)
        vnffg_name_data_map[vnffg_name]["vnffg_info"] = vnffg_info
        
        parse_log_file(log_file, vnffg_csv_file_path, vnffg_info)
        plot_duration_curves(vnffg_csv_file_path, vnffg_info.ue_list, vnffg_info.vnf_list)
        plot_vnffg_self_info(vnffg_csv_file_path, vnffg_info)
        
        print("\n\n"+"*"*10)
        print(f"{vnffg_name} finish service, revenue and cost report: ")
        print("*"*10)
        if len(vnffg_info.pkt_delay_receiver) != 0:
            print(f"{vnffg_name}_over_delay_rate: {vnffg_info.over_delay_num/len(vnffg_info.pkt_delay_receiver)}")
        if sum(vnffg_info.cost_list) != 0:
            print(f"{vnffg_name}_rev_cost_rate:",sum(vnffg_info.revenue_list)/sum(vnffg_info.cost_list))
        print("-"*10+"\n\n")
        
    plot_vnffgs_info(vnffg_name_data_map, save_path = vnffgs_log_path)
